package jp.co.worksap.ate.gomoku.model;

import java.util.ArrayList;
import java.util.List;

/**
 * This class represents the board of gomoku.
 * @author liu_j@worksap.co.jp
 *
 */
public class Goban {
	private GoState[][] goban;
	private int xSize;
	private int ySize;
	public Goban(int x, int y) {
		this.xSize = x;
		this.ySize = y;
		goban = new GoState[x][];
		for(int i=0; i<x; i++) {
			goban[i] = new GoState[y];
			for(int j=0; j<y; j++) {
				goban[i][j] = GoState.EMPTY;
			}
		}
	}
	public void putGo(int x, int y, GoState goType) {
		goban[x][y] = goType;
	}
	public GoState getGoState(int x, int y) {
		return goban[x][y];
	}
	public boolean isState(int x, int y, GoState goType) {
		return getGoState(x, y) == goType;
	}
	public boolean isEmpty(int x, int y) {
		return isState(x, y, GoState.EMPTY);
	}
	public boolean isBlack(int x, int y) {
		return isState(x, y, GoState.BLACK);
	}
	public boolean isWhite(int x, int y) {
		return isState(x, y, GoState.WHITE);
	}
	public void putBlack(int x, int y) {
		putGo(x, y, GoState.BLACK);
	}
	public void putWhite(int x, int y) {
		putGo(x, y, GoState.WHITE);
	}

	private int countGo(int x, int y, GoState goType, int offsetX, int offsetY) {
		int count = 0;
		int i = x + offsetX;
		int j = y + offsetY;
		while(i>=0 && i<goban.length && j>=0 && j<goban[i].length) {
			if(isState(i, j, goType)) {
				count++;
				i += offsetX;
				j += offsetY;
			} else {
				break;
			}
		}
		return count;
	}
	private boolean isLive(int x, int y, GoState goType, int offsetX, int offsetY) {
		int i = x + offsetX;
		int j = y + offsetY;
		while(i>=0 && i<goban.length && j>=0 && j<goban[i].length) {
			if(!isState(i, j, goType)) {
				if(isEmpty(i, j)) {
					return true;
				} else {
					return false;
				}
			} else {
				i += offsetX;
				j += offsetY;
			}
		}
		return false;
	}
	private boolean isContinuationInDirection(int x, int y, int num, GoState goType, Direction direction) {
		int offsetX = 0, offsetY = 0;
		switch(direction) {
		case H: offsetX=1; offsetY=0; break;
		case V: offsetX=0; offsetY=1; break;
		case R: offsetX=1; offsetY=-1; break;
		case L: offsetX=1; offsetY=1; break;
		}
		return (countGo(x, y, goType, offsetX, offsetY) + countGo(x, y, goType, offsetX * (-1), offsetY * (-1)) + 1) == num;
	}
	private boolean isDoubleLiveInDirection(int x, int y, int num, GoState goType, Direction direction) {
		if(!isContinuationInDirection(x, y, num, goType, direction)) {
			return false;
		}
		int offsetX = 0, offsetY = 0;
		switch(direction) {
		case H: offsetX=1; offsetY=0; break;
		case V: offsetX=0; offsetY=1; break;
		case R: offsetX=1; offsetY=-1; break;
		case L: offsetX=1; offsetY=1; break;
		}
		boolean isLive1 = isLive(x, y, goType, offsetX, offsetY);
		boolean isLive2 = isLive(x, y, goType, offsetX*(-1), offsetY*(-1));
		return isLive1 && isLive2;
	}
	private boolean isSingleLiveInDirection(int x, int y, int num, GoState goType, Direction direction) {
		if(!isContinuationInDirection(x, y, num, goType, direction)) {
			return false;
		}
		int offsetX = 0, offsetY = 0;
		switch(direction) {
		case H: offsetX=1; offsetY=0; break;
		case V: offsetX=0; offsetY=1; break;
		case R: offsetX=1; offsetY=-1; break;
		case L: offsetX=1; offsetY=1; break;
		}
		boolean isLive1 = isLive(x, y, goType, offsetX, offsetY);
		boolean isLive2 = isLive(x, y, goType, offsetX*(-1), offsetY*(-1));
		return !(isLive1 && isLive2) && (isLive1 || isLive2);
	}
	public int countContinuation(int x, int y, int num, GoState goType) {
		int count = 0;
		if(isContinuationInDirection(x, y, num, goType, Direction.H)) count++;
		if(isContinuationInDirection(x, y, num, goType, Direction.V)) count++;
		if(isContinuationInDirection(x, y, num, goType, Direction.L)) count++;
		if(isContinuationInDirection(x, y, num, goType, Direction.R)) count++;
		return count;
	}
	public int countDoubleLive(int x, int y, int num, GoState goType) {
		int count = 0;
		if(isDoubleLiveInDirection(x, y, num, goType, Direction.H)) count++;
		if(isDoubleLiveInDirection(x, y, num, goType, Direction.V)) count++;
		if(isDoubleLiveInDirection(x, y, num, goType, Direction.L)) count++;
		if(isDoubleLiveInDirection(x, y, num, goType, Direction.R)) count++;
		return count;
	}
	public int countSingleLive(int x, int y, int num, GoState goType) {
		int count = 0;
		if(isSingleLiveInDirection(x, y, num, goType, Direction.H)) count++;
		if(isSingleLiveInDirection(x, y, num, goType, Direction.V)) count++;
		if(isSingleLiveInDirection(x, y, num, goType, Direction.L)) count++;
		if(isSingleLiveInDirection(x, y, num, goType, Direction.R)) count++;
		return count;
	}
	public int getXSize() {
		return xSize;
	}
	public int getYSize() {
		return ySize;
	}
	@Override
	public Goban clone() {
		Goban newObj = new Goban(xSize, ySize);
		for(int i=0; i<xSize; i++) {
			for(int j=0; j<xSize; j++) {
				if(!isEmpty(i, j)) {
					newObj.putGo(i, j, getGoState(i, j));
				}
			}
		}
		return newObj;
	}
	
	private boolean isCoreGo(int x, int y, GoState goType) {
		int count = 0;
		if(countGo(x, y, goType, 1, 0)>0 || countGo(x, y, goType, -1, 0)>0) {
			count++;
		}
		if(countGo(x, y, goType, 0, 1)>0 || countGo(x, y, goType, 0, -1)>0) {
			count++;
		}
		if(countGo(x, y, goType, 1, 1)>0 || countGo(x, y, goType, -1, -1)>0) {
			count++;
		}
		if(countGo(x, y, goType, 1, -1)>0 || countGo(x, y, goType, -1, 1)>0) {
			count++;
		}
		
		return count >= 2;
	}
	private List<GoPoint> getRelatedGo(int x, int y, GoState goType) {
		List<GoPoint> list = new ArrayList<GoPoint>();
		addRelatedGo(x, y, goType, 1, 0, list);
		addRelatedGo(x, y, goType, -1, 0, list);
		addRelatedGo(x, y, goType, 0, 1, list);
		addRelatedGo(x, y, goType, 0, -1, list);
		addRelatedGo(x, y, goType, 1, 1, list);
		addRelatedGo(x, y, goType, -1, -1, list);
		addRelatedGo(x, y, goType, 1, -1, list);
		addRelatedGo(x, y, goType, -1, 1, list);
		return list;
	}
	private void addRelatedGo(int x, int y, GoState goType, int offsetX, int offsetY, List<GoPoint> list) {
		int i = x + offsetX;
		int j = y + offsetY;
		while(i>=0 && i<goban.length && j>=0 && j<goban[i].length) {
			if(isState(i, j, goType)) {
				GoPoint point = new GoPoint(i, j);
				list.add(point);
				i += offsetX;
				j += offsetY;
			} else {
				break;
			}
		}
	}
	public List<GoPoint> getRelatedCoreGo(int x, int y, GoState goType) {
		List<GoPoint> coreList = new ArrayList<GoPoint>();
		List<GoPoint> relatedList = getRelatedGo(x, y, goType);
		for(GoPoint point : relatedList) {
			if(isCoreGo(point.getX(), point.getY(), goType)) {
				coreList.add(point);
			}
		}
		return coreList;
	}
}
